﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SpriteAnimator.Classes;
using System.IO;

namespace SpriteAnimator
{
    /// <summary>
    /// Fenêtre de gestion des zones texture
    /// </summary>
    public partial class frmZoneTexture: Form
    {
        #region Variables d'instances

        private bool m_bSave;
        private Textures m_oZones;

        #endregion

        #region Constructeur

        /// <summary>
        /// Constructeur de la fenêtre
        /// </summary>
        /// <param name="p_sTitle">Titre du projet</param>
        public frmZoneTexture(string p_sTitle)
        {
            InitializeComponent();

            // *** Initialisation ***

            this.m_oZones = new Textures();
            this.m_oZones.Name = p_sTitle;
            this.m_oZones.BackColor = Color.FromArgb(Convert.ToInt32(Utils.GetRegistryValue("BackColor", Color.CornflowerBlue.ToArgb().ToString(), Utils.REGISTRY_KEY)));

            this.chkCadre.Checked = Utils.GetRegistryValue("SpriteRectangle", "1", Utils.REGISTRY_KEY).Equals("1");

            this.m_bSave = false;

            // *** Mise à jour interface ***

            this.BuildTitle();
            this.CheckInterface();
        }

        #endregion

        #region Evénements de l'IHM

        /// <summary>
        /// Affichage de la zone cadre pour la preview
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void chkCadre_CheckedChanged(object sender, EventArgs e)
        {
            this.PreviewZone();
        }

        /// <summary>
        /// Se produit sur l'ajoute d'une zone
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdAdd_Click(object sender, EventArgs e)
        {
            // *** Ouverture de la frame de sélection

            using ( frmFrameEdit oEdit = new frmFrameEdit(null, this.m_oZones.Texture, this.m_oZones.BackColor) )
            {
                if ( oEdit.ShowDialog(this) == DialogResult.OK )
                {
                    // *** Demande de nom ***

                    using ( frmInputBox oBox = new frmInputBox("Nom zone", "Saisissez un alias", string.Empty) )
                    {
                        bool bCancel = false;

                        // *** Saisie de l'alias ***

                        do
                        {
                            bCancel = oBox.ShowDialog() == DialogResult.Cancel;
                        }
                        while ( !bCancel && this.ExistName(oBox.Value) );

                        // *** Ajout à la liste ***

                        if ( !bCancel )
                        {
                            ListViewItem oItem = new ListViewItem();
                            oItem.Tag = oEdit.Frame;
                            oItem.Text = oBox.Value;
                            oItem.SubItems.Add(oEdit.Frame.X.ToString());
                            oItem.SubItems.Add(oEdit.Frame.Y.ToString());
                            oItem.SubItems.Add(oEdit.Frame.Width.ToString());
                            oItem.SubItems.Add(oEdit.Frame.Height.ToString());

                            this.lstFrames.Items.Add(oItem);
                            this.SetModify(true);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Click pour géré la couleur de fond
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdColor_Click(object sender, EventArgs e)
        {
            this.dlgColor.Color = this.picPreview.BackColor;

            if ( this.dlgColor.ShowDialog(this) == DialogResult.OK )
                this.SetBackground(this.dlgColor.Color);
        }

        /// <summary>
        /// Se produit sur la suppression d'une zone
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdRemove_Click(object sender, EventArgs e)
        {
            // *** Vérification ***

            if ( this.lstFrames.SelectedIndices.Count < 1 )
            {
                MessageBox.Show(this, "Vous devez sélectionner une zone.", "Suppression", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            // *** Demande de confirmation ***

            if ( MessageBox.Show(this, "Etes-vous sûr de vouloir supprimer la zone " + (this.lstFrames.SelectedIndices[0] + 1).ToString() + " ?", "Suppression", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No )
                return;

            // *** Suppression de la frame ***

            this.lstFrames.Items.RemoveAt(this.lstFrames.SelectedIndices[0]);
            this.SetModify(true);
        }

        /// <summary>
        /// Click pour géré la texture
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void cmdTexture_Click(object sender, EventArgs e)
        {
            try
            {
                // *** Demande ou est le fichier ***

                using ( frmTexture oFormTexture = new frmTexture(this.m_oZones.Texture) )
                {
                    // *** Ouverture de la fenêtre ***

                    if ( oFormTexture.ShowDialog(this) == DialogResult.Cancel )
                        return;

                    // *** Charge la texture ***

                    if ( this.m_oZones.Texture != null )
                    {
                        this.m_oZones.Texture.Dispose();
                        this.m_oZones.Texture = null;
                    }

                    if ( this.picTexture.Image != null )
                    {
                        this.picTexture.Image.Dispose();
                        this.picTexture.Image = null;
                    }

                    this.m_oZones.Texture = (Image)oFormTexture.Texture.Clone();
                    this.picTexture.Image = (Image)this.m_oZones.Texture.Clone();

                    // *** Vérification des zones ***

                    this.CheckZones();
                }

                // *** Modification ***

                this.CheckInterface();
                this.SetModify(true);
            }
            catch ( Exception ex )
            {
                frmMain.ShowError(ex);
            }
        }

        /// <summary>
        /// Se produit sur la fermeture de la fenêtre
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void frmSprite_FormClosing(object sender, FormClosingEventArgs e)
        {
            if ( this.m_bSave )
            {
                DialogResult oTmp = MessageBox.Show(this, "Des modifications ont été apporté, voulez-vous les enregistrer ?", "Zone", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information);

                if ( oTmp == DialogResult.Cancel )
                    e.Cancel = true;
                else if ( oTmp == DialogResult.Yes )
                    this.Save(false);
            }
        }

        /// <summary>
        /// Modifie une frame existante
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void lstFrames_DoubleClick(object sender, EventArgs e)
        {
            // *** Vérification ***

            if ( this.lstFrames.SelectedIndices.Count < 1 )
                return;

            ListViewItem oItem = this.lstFrames.Items[this.lstFrames.SelectedIndices[0]];

            // *** Ouverture de la frame de sélection

            using ( frmFrameEdit oEdit = new frmFrameEdit((Rectangle)oItem.Tag, this.m_oZones.Texture, this.m_oZones.BackColor) )
            {
                if ( oEdit.ShowDialog(this) == DialogResult.OK )
                {
                    oItem.Tag = oEdit.Frame;
                    oItem.SubItems[1].Text = oEdit.Frame.X.ToString();
                    oItem.SubItems[2].Text = oEdit.Frame.Y.ToString();
                    oItem.SubItems[3].Text = oEdit.Frame.Width.ToString();
                    oItem.SubItems[4].Text = oEdit.Frame.Height.ToString();

                    this.SetModify(true);
                }
            }
        }

        /// <summary>
        /// Changement de sélection
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void lstFrames_SelectedIndexChanged(object sender, EventArgs e)
        {
            this.PreviewZone();
            this.CheckInterface();
        }

        /// <summary>
        /// Affiche la texture
        /// </summary>
        /// <param name="sender">Source de l'appel</param>
        /// <param name="e">Argument de l'appel</param>
        private void picTexture_Paint(object sender, PaintEventArgs e)
        {
            // *** On efface tout ***

            e.Graphics.Clear(this.picTexture.BackColor);

            // *** Rescale ? ***

            if ( this.picTexture.Image != null )
            {
                Rectangle oFinal;

                if ( this.picTexture.Image.Width > this.picTexture.Width || this.picTexture.Image.Height > this.picTexture.Height )
                {
                    if ( this.picTexture.Image.Width > this.picTexture.Image.Height )
                    {
                        oFinal = new Rectangle(0, 0, this.picTexture.Width, Convert.ToInt32(Convert.ToSingle(this.picTexture.Width) * (Convert.ToSingle(this.picTexture.Image.Height) / Convert.ToSingle(this.picTexture.Image.Width))));
                        oFinal.Y = (this.picTexture.Height - oFinal.Height) / 2;
                    }
                    else
                    {
                        oFinal = new Rectangle(0, 0, Convert.ToInt32(Convert.ToSingle(this.picTexture.Height) * (Convert.ToSingle(this.picTexture.Image.Width) / Convert.ToSingle(this.picTexture.Image.Height))), this.picTexture.Height);
                        oFinal.X = (this.picTexture.Width - oFinal.Width) / 2;
                    }
                }
                else
                    oFinal = new Rectangle((this.picTexture.Width / 2) - (this.picTexture.Image.Width / 2), (this.picTexture.Height / 2) - (this.picTexture.Image.Height / 2), this.picTexture.Image.Width, this.picTexture.Image.Height);

                // *** On dessine ***

                e.Graphics.DrawImage(this.picTexture.Image, oFinal, new Rectangle(0, 0, this.picTexture.Image.Width, this.picTexture.Image.Height), GraphicsUnit.Pixel);
            }
        }

        #endregion

        #region Fonctions privées

        /// <summary>
        /// Défini le titre de la fenêtre
        /// </summary>
        private void BuildTitle()
        {
            this.Text = String.Format("Texture Zones - {0} {1}", this.m_oZones.Name, this.m_bSave ? "(Modifié)" : String.Empty);
        }

        /// <summary>
        /// Remet à jour les status des boutons
        /// </summary>
        private void CheckInterface()
        {
            this.cmdAdd.Enabled = this.m_oZones.Texture != null;
            this.cmdRemove.Enabled = this.m_oZones.Texture != null && this.lstFrames.SelectedIndices.Count > 0;
        }

        /// <summary>
        /// Vérifie la validité des zones
        /// </summary>
        private void CheckZones()
        {
            List<int> oRemoves = new List<int>();

            // *** Vérifications ***

            foreach ( ListViewItem oItem in this.lstFrames.Items )
            {
                Rectangle oRect = (Rectangle)oItem.Tag;

                if ( (oRect.X + oRect.Width) > this.m_oZones.Texture.Width || (oRect.Y + oRect.Height) > this.m_oZones.Texture.Height )
                    oRemoves.Add(oItem.Index);
            }

            // *** Erreurs détectées ***

            if ( oRemoves.Count > 0 )
            {
                // *** Suppression ***

                for ( int i = oRemoves.Count - 1; i > -1; i-- )
                    this.lstFrames.Items.RemoveAt(oRemoves[i]);

                // *** On avertis ***

                MessageBox.Show(this, "Des zones n'étaient plus compatibles avec la nouvelle texture.\nElles ont été supprimées.", "Zone", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        /// <summary>
        /// Vérifie l'existance d'un alias
        /// </summary>
        /// <param name="p_sName">Alias a tester</param>
        /// <returns>TRUE existe déjà, FALSE sinon</returns>
        private bool ExistName(string p_sName)
        {
            foreach ( ListViewItem oItem in this.lstFrames.Items )
            {
                if ( oItem.Text.Trim().ToLower().Equals(p_sName) )
                    return (true);
            }

            return (false);
        }

        /// <summary>
        /// Lance la prévisualisation de la zone
        /// </summary>
        private void PreviewZone()
        {
            Rectangle oBounds;

            // *** Libération de l'ancienne image ***

            if ( this.picPreview.Image != null )
            {
                this.picPreview.Image.Dispose();
                this.picPreview.Image = null;
            }

            // *** Vérification ***

            if ( this.lstFrames.SelectedIndices.Count > 0 )
            {
                // *** Génération des bornes ***

                oBounds = (Rectangle)this.lstFrames.Items[this.lstFrames.SelectedIndices[0]].Tag;

                // *** Génération de l'image ***

                Bitmap oImg = new Bitmap(oBounds.Width, oBounds.Height);

                // *** On récupère la zone de dessin ***
                using ( Graphics oGraphics = Graphics.FromImage(oImg) )
                {
                    // *** Ecriture de l'image ***

                    oGraphics.DrawImage(this.m_oZones.Texture, new Rectangle(0, 0, oBounds.Width, oBounds.Height), oBounds, GraphicsUnit.Pixel);

                    // *** Cadre ***

                    if ( this.chkCadre.Checked )
                    {
                        using ( Pen oPen = new Pen(Color.Black) )
                        {
                            oGraphics.DrawRectangle(oPen, new Rectangle(0, 0, oBounds.Width - 1, oBounds.Height - 1));
                        }
                    }
                }

                // *** Ajout au contrôle ***

                this.picPreview.Image = oImg;
            }

            // *** Ré affichage ***

            this.picPreview.Invalidate();
        }

        /// <summary>
        /// Rafraichis la liste des zones dans l'objet métier
        /// </summary>
        private void RefreshZone()
        {
            this.m_oZones.Zones.Clear();

            foreach ( ListViewItem oItem in this.lstFrames.Items )
                this.m_oZones.Zones.Add(oItem.Text, (Rectangle)oItem.Tag);
        }

        /// <summary>
        /// Affecte la couleur de fond
        /// </summary>
        /// <param name="p_oColor">Couleur de fond</param>
        public void SetBackground(Color p_oColor)
        {
            this.picTexture.BackColor = p_oColor;
            this.picPreview.BackColor = p_oColor;
            this.m_oZones.BackColor = p_oColor;

            // *** Modification ***

            this.SetModify(true);
        }

        /// <summary>
        /// Modifie le statut de modification
        /// </summary>
        /// <param name="p_bMode">TRUE modifié, FALSE sinon</param>
        private void SetModify(bool p_bMode)
        {
            this.m_bSave = p_bMode;
            this.BuildTitle();
        }

        #endregion

        #region Fonctions publiques

        /// <summary>
        /// Charge une définitin de sprite déjà existante
        /// </summary>
        /// <param name="p_sPath">Chemin de la définition</param>
        public void LoadTextureZone(string p_sPath)
        {
            // *** Chargement ***

            this.m_oZones.Load(p_sPath);

            // *** Mise à jour texture ***

            this.picTexture.Image = (Image)this.m_oZones.Texture.Clone();

            // *** Ajout des informations

            this.PopulateZones();
            this.m_bSave = false;

            // *** Mise à jour interface ***

            this.BuildTitle();
            this.CheckInterface();

            // *** Mise à jour globale ***

            this.Invalidate();
        }

        /// <summary>
        /// Remplis la liste des zones
        /// </summary>
        private void PopulateZones()
        {
            this.lstFrames.Items.Clear();

            foreach ( KeyValuePair<string, Rectangle> oZone in this.m_oZones.Zones )
            {
                ListViewItem oItem = new ListViewItem();

                oItem.Tag = oZone.Value;
                oItem.Text = oZone.Key;
                oItem.SubItems.Add(oZone.Value.X.ToString());
                oItem.SubItems.Add(oZone.Value.Y.ToString());
                oItem.SubItems.Add(oZone.Value.Width.ToString());
                oItem.SubItems.Add(oZone.Value.Height.ToString());

                this.lstFrames.Items.Add(oItem);
            }
        }

        /// <summary>
        /// Sauvegarde le sprite en cours
        /// </summary>
        /// <param name="p_bShowLocation">TRUE force de précisé le chemin</param>
        public void Save(bool p_bShowLocation)
        {
            string sPath;
            string sName;

            // *** Vérification ***

            if ( this.m_oZones.Texture == null )
            {
                MessageBox.Show(this, "Vous devez au moins spécifier une texture pour pouvoir enregistrer.", "Sprite", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            try
            {
                // *** Demande de chemin ***

                if ( p_bShowLocation || String.IsNullOrEmpty(this.m_oZones.FilePath) )
                {
                    this.dlgSave.FileName = this.m_oZones.FilePath;

                    if ( String.IsNullOrEmpty(this.dlgSave.FileName) )
                        this.dlgSave.FileName = this.m_oZones.Name;

                    if ( this.dlgSave.ShowDialog(this) == DialogResult.Cancel )
                        return;

                    sPath = Path.GetDirectoryName(this.dlgSave.FileName);
                    sName = Path.GetFileNameWithoutExtension(this.dlgSave.FileName);
                }
                else
                {
                    sPath = Path.GetDirectoryName(this.m_oZones.FilePath);
                    sName = Path.GetFileNameWithoutExtension(this.m_oZones.FilePath);
                }

                // *** On rafraichis les données ***

                this.RefreshZone();
                
                // *** Enregistrement ***
                
                this.m_oZones.Save(sPath, sName);
                this.SetModify(false);
            }
            catch ( Exception e )
            {
                MessageBox.Show(this, String.Format("Un erreur est survenue pendant l'enregistrement.\n\n{0}", e.Message), "Sprite", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        #endregion
    }
}
